View Javadoc

1   package org.apache.maven.surefire.junitcore.pc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
23  import org.apache.maven.surefire.testset.TestSetFailedException;
24  import org.junit.internal.runners.ErrorReportingRunner;
25  import org.junit.runner.Description;
26  import org.junit.runner.Runner;
27  import org.junit.runner.manipulation.Filter;
28  import org.junit.runner.manipulation.NoTestsRemainException;
29  import org.junit.runner.notification.RunNotifier;
30  import org.junit.runners.ParentRunner;
31  import org.junit.runners.Suite;
32  import org.junit.runners.model.InitializationError;
33  import org.junit.runners.model.RunnerBuilder;
34  
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.EnumMap;
39  import java.util.Iterator;
40  import java.util.LinkedHashSet;
41  import java.util.Map;
42  import java.util.concurrent.ExecutorService;
43  import java.util.concurrent.Executors;
44  
45  import static org.apache.maven.surefire.junitcore.pc.ParallelComputerUtil.*;
46  import static org.apache.maven.surefire.junitcore.pc.Type.*;
47  
48  /**
49   * Executing suites, classes and methods with defined concurrency. In this example the threads which completed
50   * the suites and classes can be reused in parallel methods.
51   * <pre>
52   * JUnitCoreParameters parameters = ...;
53   * ParallelComputerBuilder builder = new ParallelComputerBuilder(parameters);
54   * builder.useOnePool(8).parallelSuites(2).parallelClasses(4).parallelMethods();
55   * ParallelComputerBuilder.ParallelComputer computer = builder.buildComputer();
56   * Class<?>[] tests = {...};
57   * new JUnitCore().run(computer, tests);
58   * </pre>
59   * Note that the type has always at least one thread even if unspecified. The capacity in
60   * {@link ParallelComputerBuilder#useOnePool(int)} must be greater than the number of concurrent suites and classes
61   * altogether.
62   * <p/>
63   * The Computer can be shutdown in a separate thread. Pending tests will be interrupted if the argument is
64   * <tt>true</tt>.
65   * <pre>
66   * computer.shutdown(true);
67   * </pre>
68   *
69   * @author Tibor Digana (tibor17)
70   * @since 2.16
71   */
72  public final class ParallelComputerBuilder
73  {
74      static final int TOTAL_POOL_SIZE_UNDEFINED = 0;
75  
76      private final Map<Type, Integer> parallelGroups = new EnumMap<Type, Integer>( Type.class );
77  
78      private boolean useSeparatePools;
79  
80      private int totalPoolSize;
81  
82      private JUnitCoreParameters parameters;
83  
84      private boolean optimize;
85  
86      private boolean runningInTests;
87  
88      /**
89       * Calling {@link #useSeparatePools()}.
90       * Can be used only in unit tests.
91       * Do NOT call this constructor in production.
92       */
93      ParallelComputerBuilder()
94      {
95          runningInTests = true;
96          useSeparatePools();
97          parallelGroups.put( SUITES, 0 );
98          parallelGroups.put( CLASSES, 0 );
99          parallelGroups.put( METHODS, 0 );
100     }
101 
102     public ParallelComputerBuilder( JUnitCoreParameters parameters )
103     {
104         this();
105         runningInTests = false;
106         this.parameters = parameters;
107     }
108 
109     public ParallelComputer buildComputer()
110     {
111         return new PC();
112     }
113 
114     ParallelComputerBuilder useSeparatePools()
115     {
116         totalPoolSize = TOTAL_POOL_SIZE_UNDEFINED;
117         useSeparatePools = true;
118         return this;
119     }
120 
121     ParallelComputerBuilder useOnePool()
122     {
123         totalPoolSize = TOTAL_POOL_SIZE_UNDEFINED;
124         useSeparatePools = false;
125         return this;
126     }
127 
128     /**
129      * @param totalPoolSize Pool size where suites, classes and methods are executed in parallel.
130      *                      If the <tt>totalPoolSize</tt> is {@link Integer#MAX_VALUE}, the pool capacity is not
131      *                      limited.
132      * @throws IllegalArgumentException If <tt>totalPoolSize</tt> is &lt; 1.
133      */
134     ParallelComputerBuilder useOnePool( int totalPoolSize )
135     {
136         if ( totalPoolSize < 1 )
137         {
138             throw new IllegalArgumentException( "Size of common pool is less than 1." );
139         }
140         this.totalPoolSize = totalPoolSize;
141         useSeparatePools = false;
142         return this;
143     }
144 
145     boolean isOptimized()
146     {
147         return optimize;
148     }
149 
150     ParallelComputerBuilder optimize( boolean optimize )
151     {
152         this.optimize = optimize;
153         return this;
154     }
155 
156     ParallelComputerBuilder parallelSuites()
157     {
158         return parallel( SUITES );
159     }
160 
161     ParallelComputerBuilder parallelSuites( int nThreads )
162     {
163         return parallel( nThreads, SUITES );
164     }
165 
166     ParallelComputerBuilder parallelClasses()
167     {
168         return parallel( CLASSES );
169     }
170 
171     ParallelComputerBuilder parallelClasses( int nThreads )
172     {
173         return parallel( nThreads, CLASSES );
174     }
175 
176     ParallelComputerBuilder parallelMethods()
177     {
178         return parallel( METHODS );
179     }
180 
181     ParallelComputerBuilder parallelMethods( int nThreads )
182     {
183         return parallel( nThreads, METHODS );
184     }
185 
186     private ParallelComputerBuilder parallel( int nThreads, Type parallelType )
187     {
188         if ( nThreads < 0 )
189         {
190             throw new IllegalArgumentException( "negative nThreads " + nThreads );
191         }
192 
193         if ( parallelType == null )
194         {
195             throw new NullPointerException( "null parallelType" );
196         }
197 
198         parallelGroups.put( parallelType, nThreads );
199         return this;
200     }
201 
202     private ParallelComputerBuilder parallel( Type parallelType )
203     {
204         return parallel( Integer.MAX_VALUE, parallelType );
205     }
206 
207     private double parallelTestsTimeoutInSeconds()
208     {
209         return parameters == null ? 0d : parameters.getParallelTestsTimeoutInSeconds();
210     }
211 
212     private double parallelTestsTimeoutForcedInSeconds()
213     {
214         return parameters == null ? 0d : parameters.getParallelTestsTimeoutForcedInSeconds();
215     }
216 
217     final class PC
218         extends ParallelComputer
219     {
220         final Collection<ParentRunner> suites = new LinkedHashSet<ParentRunner>();
221 
222         final Collection<ParentRunner> nestedSuites = new LinkedHashSet<ParentRunner>();
223 
224         final Collection<ParentRunner> classes = new LinkedHashSet<ParentRunner>();
225 
226         final Collection<ParentRunner> nestedClasses = new LinkedHashSet<ParentRunner>();
227 
228         final Collection<Runner> unscheduledRunners = new LinkedHashSet<Runner>();
229 
230         int poolCapacity;
231 
232         boolean splitPool;
233 
234         private final Map<Type, Integer> allGroups;
235 
236         private long nestedClassesChildren;
237 
238         private volatile Scheduler master;
239 
240         private PC()
241         {
242             super( parallelTestsTimeoutInSeconds(), parallelTestsTimeoutForcedInSeconds() );
243             allGroups = new EnumMap<Type, Integer>( ParallelComputerBuilder.this.parallelGroups );
244             poolCapacity = ParallelComputerBuilder.this.totalPoolSize;
245             splitPool = ParallelComputerBuilder.this.useSeparatePools;
246         }
247 
248         @Override
249         public Collection<Description> shutdown( boolean shutdownNow )
250         {
251             final Scheduler master = this.master;
252             return master == null ? Collections.<Description>emptyList() : master.shutdown( shutdownNow );
253         }
254 
255         @Override
256         public Runner getSuite( RunnerBuilder builder, Class<?>[] cls )
257             throws InitializationError
258         {
259             try
260             {
261                 super.getSuite( builder, cls );
262                 populateChildrenFromSuites();
263 
264                 WrappedRunners suiteSuites = wrapRunners( suites );
265                 WrappedRunners suiteClasses = wrapRunners( classes );
266 
267                 long suitesCount = suites.size();
268                 long classesCount = classes.size() + nestedClasses.size();
269                 long methodsCount = suiteClasses.embeddedChildrenCount + nestedClassesChildren;
270                 if (!ParallelComputerBuilder.this.runningInTests)
271                 {
272                     determineThreadCounts( suitesCount, classesCount, methodsCount );
273                 }
274 
275                 return setSchedulers( suiteSuites.wrappingSuite, suiteClasses.wrappingSuite );
276             }
277             catch ( TestSetFailedException e )
278             {
279                 throw new InitializationError( e );
280             }
281         }
282 
283         @Override
284         protected Runner getRunner( RunnerBuilder builder, Class<?> testClass )
285             throws Throwable
286         {
287             Runner runner = super.getRunner( builder, testClass );
288             if ( canSchedule( runner ) )
289             {
290                 if ( runner instanceof Suite )
291                 {
292                     suites.add( (Suite) runner );
293                 }
294                 else
295                 {
296                     classes.add( (ParentRunner) runner );
297                 }
298             }
299             else
300             {
301                 unscheduledRunners.add( runner );
302             }
303             return runner;
304         }
305 
306         private void determineThreadCounts( long suites, long classes, long methods )
307             throws TestSetFailedException
308         {
309             final JUnitCoreParameters parameters = ParallelComputerBuilder.this.parameters;
310             final boolean optimize = ParallelComputerBuilder.this.optimize;
311             RunnerCounter counts = new RunnerCounter( suites, classes, methods );
312             Concurrency concurrency = resolveConcurrency( parameters, optimize ? counts : null );
313             allGroups.put( SUITES, concurrency.suites );
314             allGroups.put( CLASSES, concurrency.classes );
315             allGroups.put( METHODS, concurrency.methods );
316             poolCapacity = concurrency.capacity;
317             splitPool &= concurrency.capacity <= 0;//fault if negative; should not happen
318         }
319 
320         private <T extends Runner> WrappedRunners wrapRunners( Collection<T> runners )
321             throws InitializationError
322         {
323             // Do NOT use allGroups here.
324             long childrenCounter = 0;
325             ArrayList<Runner> runs = new ArrayList<Runner>();
326             for ( T runner : runners )
327             {
328                 if ( runner != null )
329                 {
330                     int children = countChildren( runner );
331                     childrenCounter += children;
332                     if ( children != 0 )
333                     {
334                         runs.add( runner );
335                     }
336                 }
337             }
338 
339             Suite wrapper = runs.isEmpty() ? null : new Suite( null, runs )
340             {
341             };
342             return new WrappedRunners( wrapper, childrenCounter );
343         }
344 
345         private int countChildren( Runner runner )
346         {
347             Description description = runner.getDescription();
348             Collection children = description == null ? null : description.getChildren();
349             return children == null ? 0 : children.size();
350         }
351 
352         private ExecutorService createPool( int poolSize )
353         {
354             return poolSize < Integer.MAX_VALUE
355                 ? Executors.newFixedThreadPool( poolSize )
356                 : Executors.newCachedThreadPool();
357         }
358 
359         private Scheduler createMaster( ExecutorService pool, int poolSize )
360         {
361             if ( !areSuitesAndClassesParallel() || poolSize <= 1 )
362             {
363                 return new Scheduler( null, new InvokerStrategy() );
364             }
365             else if ( pool != null && poolSize == Integer.MAX_VALUE )
366             {
367                 return new Scheduler( null, new SharedThreadPoolStrategy( pool ) );
368             }
369             else
370             {
371                 return new Scheduler( null, SchedulingStrategies.createParallelStrategy( 2 ) );
372             }
373         }
374 
375         private boolean areSuitesAndClassesParallel()
376         {
377             return !suites.isEmpty() && allGroups.get( SUITES ) > 0 && !classes.isEmpty()
378                 && allGroups.get( CLASSES ) > 0;
379         }
380 
381         private void populateChildrenFromSuites()
382         {
383             // Do NOT use allGroups here.
384             Filter filter = new SuiteFilter();
385             for ( Iterator<ParentRunner> it = suites.iterator(); it.hasNext(); )
386             {
387                 ParentRunner suite = it.next();
388                 try
389                 {
390                     suite.filter( filter );
391                 }
392                 catch ( NoTestsRemainException e )
393                 {
394                     it.remove();
395                 }
396             }
397         }
398 
399         private int totalPoolSize()
400         {
401             if ( poolCapacity == TOTAL_POOL_SIZE_UNDEFINED )
402             {
403                 int total = 0;
404                 for ( int nThreads : allGroups.values() )
405                 {
406                     total += nThreads;
407                     if ( total < 0 )
408                     {
409                         total = Integer.MAX_VALUE;
410                         break;
411                     }
412                 }
413                 return total;
414             }
415             else
416             {
417                 return poolCapacity;
418             }
419         }
420 
421         private Runner setSchedulers( ParentRunner suiteSuites, ParentRunner suiteClasses )
422             throws InitializationError
423         {
424             int parallelSuites = allGroups.get( SUITES );
425             int parallelClasses = allGroups.get( CLASSES );
426             int parallelMethods = allGroups.get( METHODS );
427             int poolSize = totalPoolSize();
428             ExecutorService commonPool = splitPool || poolSize == 0 ? null : createPool( poolSize );
429             master = createMaster( commonPool, poolSize );
430 
431             if ( suiteSuites != null )
432             {
433                 // a scheduler for parallel suites
434                 if ( commonPool != null && parallelSuites > 0 )
435                 {
436                     Balancer balancer = BalancerFactory.createBalancerWithFairness( parallelSuites );
437                     suiteSuites.setScheduler( createScheduler( null, commonPool, true, balancer ) );
438                 }
439                 else
440                 {
441                     suiteSuites.setScheduler( createScheduler( parallelSuites ) );
442                 }
443             }
444 
445             // schedulers for parallel classes
446             ArrayList<ParentRunner> allSuites = new ArrayList<ParentRunner>( suites );
447             allSuites.addAll( nestedSuites );
448             if ( suiteClasses != null )
449             {
450                 allSuites.add( suiteClasses );
451             }
452             if ( !allSuites.isEmpty() )
453             {
454                 setSchedulers( allSuites, parallelClasses, commonPool );
455             }
456 
457             // schedulers for parallel methods
458             ArrayList<ParentRunner> allClasses = new ArrayList<ParentRunner>( classes );
459             allClasses.addAll( nestedClasses );
460             if ( !allClasses.isEmpty() )
461             {
462                 setSchedulers( allClasses, parallelMethods, commonPool );
463             }
464 
465             // resulting runner for Computer#getSuite() scheduled by master scheduler
466             ParentRunner all = createFinalRunner( suiteSuites, suiteClasses );
467             all.setScheduler( master );
468             return all;
469         }
470 
471         private ParentRunner createFinalRunner( Runner... runners )
472             throws InitializationError
473         {
474             ArrayList<Runner> all = new ArrayList<Runner>( unscheduledRunners );
475             for ( Runner runner : runners )
476             {
477                 if ( runner != null )
478                 {
479                     all.add( runner );
480                 }
481             }
482 
483             return new Suite( null, all )
484             {
485                 @Override
486                 public void run( RunNotifier notifier )
487                 {
488                     try
489                     {
490                         beforeRunQuietly();
491                         super.run( notifier );
492                     }
493                     finally
494                     {
495                         afterRunQuietly();
496                     }
497                 }
498             };
499         }
500 
501         private void setSchedulers( Iterable<? extends ParentRunner> runners, int poolSize, ExecutorService commonPool )
502         {
503             if ( commonPool != null )
504             {
505                 Balancer concurrencyLimit = BalancerFactory.createBalancerWithFairness( poolSize );
506                 boolean doParallel = poolSize > 0;
507                 for ( ParentRunner runner : runners )
508                 {
509                     runner.setScheduler(
510                         createScheduler( runner.getDescription(), commonPool, doParallel, concurrencyLimit ) );
511                 }
512             }
513             else
514             {
515                 ExecutorService pool = null;
516                 if ( poolSize == Integer.MAX_VALUE )
517                 {
518                     pool = Executors.newCachedThreadPool();
519                 }
520                 else if ( poolSize > 0 )
521                 {
522                     pool = Executors.newFixedThreadPool( poolSize );
523                 }
524                 boolean doParallel = pool != null;
525                 for ( ParentRunner runner : runners )
526                 {
527                     runner.setScheduler( createScheduler( runner.getDescription(), pool, doParallel,
528                                                           BalancerFactory.createInfinitePermitsBalancer() ) );
529                 }
530             }
531         }
532 
533         private Scheduler createScheduler( Description desc, ExecutorService pool, boolean doParallel,
534                                            Balancer concurrency )
535         {
536             doParallel &= pool != null;
537             SchedulingStrategy strategy = doParallel ? new SharedThreadPoolStrategy( pool ) : new InvokerStrategy();
538             return new Scheduler( desc, master, strategy, concurrency );
539         }
540 
541         private Scheduler createScheduler( int poolSize )
542         {
543             if ( poolSize == Integer.MAX_VALUE )
544             {
545                 return new Scheduler( null, master, SchedulingStrategies.createParallelStrategyUnbounded() );
546             }
547             else if ( poolSize == 0 )
548             {
549                 return new Scheduler( null, master, new InvokerStrategy() );
550             }
551             else
552             {
553                 return new Scheduler( null, master, SchedulingStrategies.createParallelStrategy( poolSize ) );
554             }
555         }
556 
557         private boolean canSchedule( Runner runner )
558         {
559             return !( runner instanceof ErrorReportingRunner ) && runner instanceof ParentRunner;
560         }
561 
562         private class SuiteFilter
563             extends Filter
564         {
565             // Do NOT use allGroups in SuiteFilter.
566 
567             @Override
568             public boolean shouldRun( Description description )
569             {
570                 return true;
571             }
572 
573             @Override
574             public void apply( Object child )
575                 throws NoTestsRemainException
576             {
577                 super.apply( child );
578                 if ( child instanceof Suite )
579                 {
580                     nestedSuites.add( (Suite) child );
581                 }
582                 else if ( child instanceof ParentRunner )
583                 {
584                     ParentRunner parentRunner = (ParentRunner) child;
585                     nestedClasses.add( parentRunner );
586                     nestedClassesChildren += parentRunner.getDescription().getChildren().size();
587                 }
588             }
589 
590             @Override
591             public String describe()
592             {
593                 return "";
594             }
595         }
596     }
597 }